package jsr292.mock;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class Main {
    public static void main(String[] args) throws IOException {
        JarFile input = new JarFile(args[0]);
        try {
            JarOutputStream outputStream = new JarOutputStream(
                    new FileOutputStream("jsr292-mock.jar"));
            try {
                for(JarEntry entry : Collections.list(input.entries())) {
                    String name = entry.getName();
                    if (!name.endsWith(".class")) {
                        System.err.println("skipping "+name);
                        continue;
                    }

                    InputStream inputStream = input.getInputStream(entry);
                    try {
                        outputStream.putNextEntry(new JarEntry(name));
                        adapt(inputStream, outputStream);
                        outputStream.closeEntry();
                    } finally {
                        inputStream.close();
                    }
                }
            } finally {
                outputStream.close();    
            }
        } finally {
            input.close();
        }
    }

    private static void adapt(InputStream inputStream, JarOutputStream outputStream) throws IOException {
	ClassReader reader = new ClassReader(inputStream);
	ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
	reader.accept(new ClassVisitor(Opcodes.ASM4, writer) {
	    private String owner;
	    
	    @Override
	    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
	        owner = name;
	        super.visit(Opcodes.V1_5, access, name, signature, superName, interfaces);
	    }
	    
	    @Override
	    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
		if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) {
		    return null;
		}

		return super.visitField(access, name, desc, signature, value);
	    }

	    @Override
	    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		if ((access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) == 0) {
		    return null;
		}

		// filter out invoke() and invokeExact() 
		if ((name.equals("invoke") || name.equals("invokeExact"))
		        && "java/lang/invoke/MethodHandle".equals(owner)) {
		    return null;
		}
		
		final MethodVisitor delegate = super.visitMethod(access, name, desc, signature, exceptions);
		return new MethodVisitor(Opcodes.ASM4) {
		    @Override
		    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			return delegate.visitAnnotation(desc, visible);
		    }
		    @Override
		    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
		        return delegate.visitParameterAnnotation(parameter, desc, visible);
		    }
		    @Override
		    public AnnotationVisitor visitAnnotationDefault() {
			return delegate.visitAnnotationDefault();
		    }
		    @Override
		    public void visitAttribute(Attribute attr) {
			delegate.visitAttribute(attr);
		    }
		    @Override
		    public void visitCode() {
			delegate.visitCode();

			delegate.visitTypeInsn(Opcodes.NEW, "java/lang/UnsupportedOperationException");
			delegate.visitInsn(Opcodes.DUP);
			delegate.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/UnsupportedOperationException",
				"<init>", "()V");
			delegate.visitInsn(Opcodes.ATHROW);
		    }
		    @Override
		    public void visitMaxs(int maxStack, int maxLocals) {
			delegate.visitMaxs(maxStack, maxLocals);
		    }
		    @Override
		    public void visitEnd() {
			delegate.visitEnd();
		    }		  
		};
	    }
	}, ClassReader.SKIP_FRAMES);   

	outputStream.write(writer.toByteArray());
    }
}
